#include "serial_port.h" 

#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <stdexcept> 

#include <cstring>   

#include <cerrno>    


#include "hilog/log.h"

#define LOG_DOMAIN 0x0000 

#undef LOG_TAG
#define LOG_TAG "SerialPortLib" 


SerialPort::SerialPort() : fd_(-1) {}

SerialPort::~SerialPort() {
    closePort();
}

int SerialPort::openPort(const std::string& portName, int baudRate) {
    if (fd_ != -1) {
        

        OH_LOG_INFO(LOG_APP, "Port %{public}s was open, closing it first.", port_name_.c_str());
        closePort();
    }

    

    fd_ = open(portName.c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd_ == -1) {
        OH_LOG_ERROR(LOG_APP, "Error: Unable to open serial port %{public}s: %{public}s", portName.c_str(), strerror(errno));
        return -1;
    }

    port_name_ = portName; 

    

    fcntl(fd_, F_SETFL, 0); 

    struct termios options;
    if (tcgetattr(fd_, &options) != 0) {
        OH_LOG_ERROR(LOG_APP, "Error: tcgetattr failed for %{public}s: %{public}s", portName.c_str(), strerror(errno));
        close(fd_);
        fd_ = -1;
        return -1;
    }

    

    speed_t speed;
    switch (baudRate) {
        case 9600: speed = B9600; break;
        case 19200: speed = B19200; break;
        case 38400: speed = B38400; break;
        case 57600: speed = B57600; break;
        case 115200: speed = B115200; break;
        

        default:
            OH_LOG_ERROR(LOG_APP, "Error: Unsupported baud rate %{public}d", baudRate);
            close(fd_);
            fd_ = -1;
            return -1;
    }
    

    if (cfsetispeed(&options, speed) < 0 || cfsetospeed(&options, speed) < 0) {
        OH_LOG_ERROR(LOG_APP, "Error: cfsetispeed or cfsetospeed failed for %s: %s\n", portName.c_str(), strerror(errno));
        close(fd_);
        fd_ = -1;
        return -1;
    }
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_iflag &= ~(IXON | IXOFF | IXANY);
    options.c_cflag &= ~CRTSCTS;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ISIG);
    options.c_oflag &= ~OPOST;
    options.c_cc[VMIN] = 0;
    options.c_cc[VTIME] = 1;

    tcflush(fd_, TCIFLUSH); 

    if (tcsetattr(fd_, TCSANOW, &options) != 0) {
        OH_LOG_ERROR(LOG_APP, "Error: tcsetattr failed for %{public}s: %{public}s", portName.c_str(), strerror(errno));
        close(fd_);
        fd_ = -1;
        return -1;
    }
    OH_LOG_INFO(LOG_APP, "Serial port %{public}s opened successfully with baud rate %{public}d.", portName.c_str(), baudRate);
    return fd_;
}

void SerialPort::closePort() {
    if (fd_ != -1) {
        int result = close(fd_);
        if (result == 0) {
            OH_LOG_INFO(LOG_APP, "Serial port %{public}s closed.", port_name_.c_str());
        } else {
            OH_LOG_ERROR(LOG_APP, "Error closing serial port %{public}s: %{public}s", port_name_.c_str(), strerror(errno));
        }
        fd_ = -1;
        port_name_.clear();
    }
}

ssize_t SerialPort::writeData(const char* data, size_t length) {
    if (fd_ == -1) {
        OH_LOG_ERROR(LOG_APP, "Error: Serial port not open for writing.");
        return -1;
    }
    ssize_t bytes_written = write(fd_, data, length);
    if (bytes_written == -1) {
        OH_LOG_ERROR(LOG_APP, "Error writing to serial port: %{public}s", strerror(errno));
    } else if (bytes_written < length) {
        OH_LOG_WARN(LOG_APP, "Incomplete write to serial port. Wrote %{public}zd of %{public}zu bytes.", bytes_written, length);
    } else {

    }
    return bytes_written;
}

ssize_t SerialPort::readData(char* buffer, size_t bufferSize, int timeout_ms) {
    if (fd_ == -1) {
        OH_LOG_ERROR(LOG_APP, "Error: Serial port not open for reading.");
        return -1;
    }
    if (buffer == nullptr || bufferSize == 0) {
        OH_LOG_ERROR(LOG_APP, "Error: Invalid buffer for reading.");
        return -1;
    }

    fd_set read_fds;
    struct timeval timeout;

    FD_ZERO(&read_fds);
    FD_SET(fd_, &read_fds);

    timeout.tv_sec = timeout_ms / 1000;
    timeout.tv_usec = (timeout_ms % 1000) * 1000;
    

    int ret = select(fd_ + 1, &read_fds, nullptr, nullptr, (timeout_ms < 0) ? nullptr : &timeout);

    if (ret == -1) {
        OH_LOG_ERROR(LOG_APP, "Error: select failed: %{public}s", strerror(errno));
        return -1;
    } else if (ret == 0) {
        return 0;
    } else {
        if (FD_ISSET(fd_, &read_fds)) {
            ssize_t bytes_read = read(fd_, buffer, bufferSize - 1); 
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0'; 

            } else if (bytes_read == 0) {
                
            } else {
                 OH_LOG_ERROR(LOG_APP, "Error reading from serial port: %{public}s", strerror(errno));
            }
            return bytes_read;
        }
    }
    OH_LOG_WARN(LOG_APP, "select returned positive but FD_ISSET was false.");
    return -1; 
}